package org.example.synchronizdemo;

/**
 * 并发编程学习
 *
 * @Author chaigq
 * @Date 2020/10/19 11:38 下午
 * @Version 1.0
 */
public class SynchronizeStudy implements Runnable {

    /**
     * 一、synchronized原理：
     * 在多线程并发编程过程中使用广泛，可以用来解决线程安全的问题，一般成为重量级锁，随着1.6以后定义其进行了优化，为了减少锁带来的性能消耗，引入了偏向锁和轻量级锁，
     * 以及锁的存储结构和升级过程
     * <p>
     * 二、synchronized作用：
     * 1、原子性：线程互斥访问同步代码块
     * 2、可见性：synchronized解锁之前，必须将工作内存中的数据同步到主内存中，其他线程操作该变量每次都可以看到被修改后的值
     * 3、有序性：一个线程加锁，必须等到其他线程释放锁；一个线程释放锁，首先要先加锁
     * <p>
     * 三、synchronized同步原理：
     * 1、通过javap命令查看该代码的class文件，可以看出在执行同步代码块之前之后都有一个monitor字样，其中前面的是monitorenter，
     * 后面的是离开monitorexit，不难想象一个线程也执行同步代码块，首先要获取锁，而获取锁的过程就是monitorenter ，在执行完代码块之后，要释放锁，释放锁就是执行monitorexit指令。
     * 执行monitorenter指令时：线程首先会去获取锁对象，当对象没有锁时或者当前线程已经拥有了该锁，就会将锁的计数器+1；
     * 当执行monitorexit时：就会将锁计数器-1，当计数器为0时，表示锁被释放
     * 为什么会有两个monitorexit呢？
     * 防止在同步代码块中因线程异常退出，锁没有得到释放，这会造成死锁，第二个monitorexit保证在异常情况下，锁能够释放避免死锁
     * <p>
     * 四、synchronized可重入的原理
     * synchronized重入锁指一个线程获取该锁之后，该线程可以继续获取该锁，底层维护锁计数器，当线程获取该锁时，计数器+1，当释放该锁时，计数器-1，再次获得该锁，继续+1，释放该锁-1
     * 当计数器为0时，其他线程可以继续获取该锁
     * <p>
     * 五、锁优化
     * JDK 1.6之前，synchronized为重量级锁，多个线程获取同一把锁，为获取的线程阻塞，等到锁释放之后重新获取锁，线程的阻塞和唤醒与操作系统有关，极其耗费资源
     * Java对象模型：
     * 一个Java对象由，对象标记，类型指针，真实数据，内存对齐四部分组成。
     * （1）对象标记也称Mark Word字段，存储当前对象的一些运行时数据。
     * （2）类型指针，JVM根据该指针确定该对象是哪个类的实例化对象。
     * （3）真实数据自然是对象的属性值。
     * （4）内存补齐，是当数据不是对齐数的整数倍的时候，进行调整，使得对象的整体大小是对齐数的整数倍方便寻址。典型的以空间换时间的思想。
     * 对象头：对象标记和类型指针的统称
     * <p>
     * 为什么Java的任意对象都可以作为锁？
     * Java对象头中存在monitor，每个对象创建后对象头中就会存有monitor，monitor为线程私有，不同对象的monitor会不同，对象作为锁的本质时对象头中的monitor作为了锁
     * <p>
     * 偏向锁：在大部分情况下，锁不仅仅不会出现多线程竞争，还总是由同一个线程获得，为了降低获取锁的代价引入偏向锁
     * 当一个线程访问同步代码块时，对象头会存储当前线程的ID，后续该线程进入和退出同步锁的代码块时，不需要再次加锁和释放锁，直接比较对象头中是否存储了线程ID，若相等表示偏向锁偏向当前
     * 线程，就不需要再次尝试获取锁
     * 偏向锁获取（只允许一个线程拥有，存在竞争则撤销偏向锁，升级为轻量级锁）：1、获取对象头中的mark word，判断对象是否处于可偏向状态
     * 2、如果处于可偏向状态：通过CAS（比较并交换）将当前线程ID写入到mark word中，如果成功表示获得偏向锁成功，偏向锁状态变为1，并将线程ID写入到对象头中；如果失败：说明由其他线程获取
     * 了该锁，当前锁存在竞争，需要将偏向锁撤掉，换成轻量级锁
     * 3、如果当前线程已经时偏向锁状态，则需要比较对象头中的线程ID是否相等，如果相等则不需要重新获取，直接执行，不相等则说明存在竞争，偏向锁撤销升级轻量级锁
     * 4、偏向锁撤销：需要等待全局安全点（当前时间节点不存在字节码执行）会暂停拥有偏向锁的线程，检查线程是否存活：（1）线程不处于活动状态：将对象头设置为无锁状态
     * （2）线程依旧存活：拥有偏向锁的栈会被执行，遍历偏向对象的锁记录，栈中锁对象记录和对象的Mark Word 要么重新偏向于其他线程，要么恢复对象无锁或者不适合作为偏向锁
     * 5、偏向锁批量重偏向：一个线程创建了大量对象并执行同步操作，另外线程再次获取对象锁进行操作，达到阀值（默认20），此时会发生偏量锁批量重偏向
     * <p>
     * 注意：（1）偏向锁在JDK1.6和JDK 1.7中默认启用，程序运行几秒后激活；（2）应用中存在大多数竞争，建议关闭偏向锁，开启的话会造成偏向锁撤销操作引起资源浪费
     * <p>
     * 轻量级锁：两个线程交替执行，没有造成同时抢锁的情况，应用轻量级锁（线程持有锁的时间都不会太长时）
     * 轻量级锁加锁：1、在线程的栈桢中创建用于存储锁记录的空间2、将mark word 复制到锁记录中3、使用cas将对象头的Mark Word替换为指向锁记录的指针
     * 4、如果成功，当前线程获得锁，如果失败，表示存在竞争，当前线程便使用自旋锁来获取锁。
     * 自旋锁：就是指当有另外一个线程来竞争锁时，这个线 程会在原地循环等待，而不是把该线程给阻塞，直到那个获得锁的线程释放锁之后，这个线程就可以马上获得锁的。
     * 锁在原地循环的时候，是会消耗 cpu 的，就相当于 在执行一个啥也没有的 for 循环，若干次循环依旧无法获取锁，则会升级成重量级锁
     * 自旋适应锁：在 JDK1.7 开始，引入了自适应自旋锁，修改自旋锁次数的JVM参数被取消，虚拟机不再支持由用户配置自旋锁次数，而是由虚拟机自动调整。
     * 自适应意味着自旋的次数不是固定不变的，而是根据前一次在同一个锁上自旋的时间以及锁的拥有者的状态来决定。如果在同一个锁对象上，自旋等待刚刚成功获得过锁，
     * 并且持有锁的线程正在运行中，那么虚拟机就会认为这次自旋也是很有可能再次成功，进而它将允许自旋等待持续相对更长的时间。
     * 如果对于某个锁，自旋很少成功获得过，那在以后尝试获取这个锁时将可能省略掉自旋过程，直接阻塞线程，避免浪费处理器资源
     * 轻量级锁得释放：反替换，使用CAS将栈帧中获得锁录空间替换到对象头，成功没有锁竞争，锁得以释放，失败说明存在竞争，那块指向锁记录得指针有别的线程在用，因此锁膨胀升级为重量级锁。
     * 重量级锁：当多个线程共同竞争同一把锁时，竞争失败得锁会被阻塞，等到持有锁的线程将锁释放后再次唤醒阻塞的线程，因为线程的唤醒和阻塞是一个很耗费CPU资源的操作，
     * 因此此处采取自适应自旋来获取重量级锁来获取重量级锁。
     * 锁消除：消除锁是虚拟机另外一种锁的优化，这种优化更彻底，Java虚拟机在编译时，通过对运行上下文的扫描，去除不可能存在共享资源竞争的锁，通过这种方式消除没有必要的锁，
     * 可以节省毫无意义的请求锁时间。（对于线程的私有变量，不存在并发问题，没有必要加锁，即使加锁编译后，也会去掉）
     * 锁粗化:原则上，同步块的作用范围要尽量小。但是如果一系列的连续 操作都对同一个对象反复加锁和解锁，甚至加锁操作在循环体内，频繁地进行互斥同步操作也会导致不必要的性能损耗。
     * 锁粗化就是增大锁的作用域。
     */
    @Override
    public void run() {
        // 加锁操作
        synchronized (this) {
            System.out.println("hello");
        }
    }

    public static void main(String[] args) {
        SynchronizeStudy test = new SynchronizeStudy();
        Thread thread = new Thread(test);
        thread.start();
    }
}
